package com.yuandian.management.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuandian.api.management.entity.ConfMpInternalPurchase;
import com.yuandian.api.management.po.ConfMpPurchaseParams;
import com.yuandian.api.management.vo.ConfMpInternalPurchaseExcelVo;
import com.yuandian.bpm.admin.api.entity.SysDictItem;
import com.yuandian.bpm.common.core.util.R;
import com.yuandian.bpm.common.data.resolver.DictResolver;
import com.yuandian.bpm.common.excel.vo.ErrorMessage;
import com.yuandian.management.mapper.ConfMpInternalPurchaseMapper;
import com.yuandian.management.service.ConfMpInternalPurchaseService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.BindingResult;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 中台监控-内部订购单配置-Service实现
 * @author zfsn
 * @date 2023-4-20
 */
@Service
public class ConfMpInternalPurchaseServiceImpl extends ServiceImpl<ConfMpInternalPurchaseMapper, ConfMpInternalPurchase>
		implements ConfMpInternalPurchaseService {

	/**
	 * 通过唯一性编码组合获取订购单配置
	 * @param orderCode 订单编码
	 * @param suborderCode 子订单编码
	 * @param abilityCode 能力编码
	 * @param serviceCode 服务编码
	 * @param appCode 订购应用编码
	 * @return 订购单配置实体
	 */
	private ConfMpInternalPurchase getByUniqueCode(
			String orderCode, String suborderCode, String abilityCode, String serviceCode, String appCode) {
		return lambdaQuery().eq(ConfMpInternalPurchase::getOrderCode, orderCode)
				.eq(ConfMpInternalPurchase::getSuborderCode, suborderCode)
				.eq(ConfMpInternalPurchase::getAbilityCode, abilityCode)
				.eq(ConfMpInternalPurchase::getServiceCode, serviceCode)
				.eq(ConfMpInternalPurchase::getAppCode, appCode).one();
	}

	/**
	 * 分页查询内部订购单
	 * @param params 中台监控-订购单配置请求参数
	 * @return 通用返回对象
	 */
	@Override
	public R<Page<ConfMpInternalPurchase>> internalPage(ConfMpPurchaseParams<ConfMpInternalPurchase> params) {
		Page<ConfMpInternalPurchase> page = Page.of(params.getCurrent(), params.getSize());
		page.addOrder(params.orders()).orders().forEach(orderItem -> orderItem.setColumn(StrUtil.toUnderlineCase(orderItem.getColumn())));
		return R.ok(lambdaQuery()
				.like(StrUtil.isNotBlank(params.getAbilityCode()), ConfMpInternalPurchase::getAbilityCode, params.getAbilityCode())
				.like(StrUtil.isNotBlank(params.getServiceCode()), ConfMpInternalPurchase::getServiceCode, params.getServiceCode())
				.like(StrUtil.isNotBlank(params.getAbilityName()), ConfMpInternalPurchase::getAbilityName, params.getAbilityName())
				.like(StrUtil.isNotBlank(params.getServiceName()), ConfMpInternalPurchase::getServiceName, params.getServiceName())
				.like(StrUtil.isNotBlank(params.getOrderCode()), ConfMpInternalPurchase::getOrderCode, params.getOrderCode())
				.like(StrUtil.isNotBlank(params.getSuborderCode()), ConfMpInternalPurchase::getSuborderCode, params.getSuborderCode())
				.page(page));
	}

	/**
	 * 新增内部订购单
	 * @param params 中台监控-单位配置请求参数
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<Boolean> internalSave(ConfMpPurchaseParams<ConfMpInternalPurchase> params) {
		// 1、校验编码组合唯一性
		if (ObjectUtil.isNotNull(getByUniqueCode(params.getOrderCode(), params.getSuborderCode(),
				params.getAbilityCode(), params.getServiceCode(), params.getAppCode()))) {
			return R.failed("新增失败，已存在相同的订单编码 + 子订单编码 + 能力编码 + 服务编码 + 订购应用编码！");
		}
		// 2、新增订购单配置
		ConfMpInternalPurchase confMpInternalPurchase = BeanUtil.copyProperties(params, ConfMpInternalPurchase.class);
		String providerInsCode = confMpInternalPurchase.getProviderInsCode();
		String departmentCode = confMpInternalPurchase.getDepartmentCode();
		if (StrUtil.isEmpty(providerInsCode) || StrUtil.isEmpty(departmentCode)
				|| providerInsCode.length() < 3 || departmentCode.length() < 3) {
			return R.failed("新增失败，能力提供方编码或订购部门编码的长度小于3，将会导致无法回填能力提供方单位编码和订购单位编码！");
		}
		confMpInternalPurchase.setProviderOrgCode(providerInsCode.substring(0, 3));
		confMpInternalPurchase.setOrgCode(departmentCode.substring(0, 3));
		save(confMpInternalPurchase);
		return R.ok(Boolean.TRUE, "新增成功");
	}

	/**
	 * 主键修改内部订购单
	 * @param params 中台监控-单位配置请求参数
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<Boolean> internalUpdateById(ConfMpPurchaseParams<ConfMpInternalPurchase> params) {
		// 1、校验请求参数
		Integer id = params.getId();
		if (id == null) {
			return R.failed("更新失败，订购单id不能为空！");
		}
		// 2、通过id获取订购单配置
		ConfMpInternalPurchase purchase = getById(id);
		// 3、校验该配置是否存在
		if (ObjectUtil.isNull(purchase)) {
			return R.failed("更新失败，不存在该订购单配置！");
		}
		// 4、校验编码组合的唯一性
		ConfMpInternalPurchase purchaseByUniqueCode = getByUniqueCode(params.getOrderCode(), params.getSuborderCode(),
				params.getAbilityCode(), params.getServiceCode(), params.getAppCode());
		if (ObjectUtil.isNotNull(purchaseByUniqueCode) && !id.equals(purchaseByUniqueCode.getId())) {
			return R.failed("修改失败，已存在相同的订单编码 + 子订单编码 + 能力编码 + 服务编码 + 订购应用编码！");
		}
		// 5、校验服务提供方编码和订购部门编码
		BeanUtil.copyProperties(params, purchase);
		String providerInsCode = purchase.getProviderInsCode();
		String departmentCode = purchase.getDepartmentCode();
		if (StrUtil.isEmpty(providerInsCode) || StrUtil.isEmpty(departmentCode)
				|| providerInsCode.length() < 3 || departmentCode.length() < 3) {
			return R.failed("修改失败，能力提供方编码或订购部门编码的长度小于3，将会导致无法回填能力提供方单位编码和订购单位编码！");
		}
		purchase.setProviderOrgCode(providerInsCode.substring(0, 3));
		purchase.setOrgCode(departmentCode.substring(0, 3));
		// 5、更新订购单配置
		updateById(purchase);
		return R.ok(Boolean.TRUE, "更新成功");
	}

	/**
	 * 主键删除内部订购单
	 * @param idList 主键集合
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<Boolean> internalRemoveByIds(List<Integer> idList) {
		// 1、校验请求参数
		if (CollUtil.isEmpty(idList)) {
			return R.failed("删除失败，主键集合不能为空");
		}
		// 2、删除订购单配置
		removeByIds(idList);
		return R.ok(Boolean.TRUE, "删除成功");
	}

	/**
	 * 导入内部订购单配置
	 * @param excelVoList 导入的数据集
	 * @param bindingResult 用来校验 实体 jsr303 校验失败结果
	 * @return 通用返回对象
	 */
	@Transactional(rollbackFor = Exception.class)
	@Override
	public R<List<ErrorMessage>> internalImport(List<ConfMpInternalPurchaseExcelVo> excelVoList, BindingResult bindingResult) {
		// 1、校验请求参数
		if (CollUtil.isEmpty(excelVoList)) {
			return R.failed("导入失败，Excel为空或解析失败！");
		}
		// 2、获取字典信息
		Map<String, String> suborderTypeMap = DictResolver.getDictItemsByType("mp_suborder_type").stream()
				.collect(Collectors.toMap(SysDictItem::getLabel, SysDictItem::getItemValue, (k1, k2) -> k1));
		Map<String, String> suborderStatusMap = DictResolver.getDictItemsByType("mp_suborder_status").stream()
				.collect(Collectors.toMap(SysDictItem::getLabel, SysDictItem::getItemValue, (k1, k2) -> k1));
		Map<String, String> serviceGrantResultMap = DictResolver.getDictItemsByType("mp_service_grant_result").stream()
				.collect(Collectors.toMap(SysDictItem::getLabel, SysDictItem::getItemValue, (k1, k2) -> k1));
		// 3、获取订购单配置唯一性校验集
		Map<String, Integer> uniqueInternalMap = lambdaQuery()
				.select(ConfMpInternalPurchase::getId,
						ConfMpInternalPurchase::getOrderCode,
						ConfMpInternalPurchase::getSuborderCode,
						ConfMpInternalPurchase::getAbilityCode,
						ConfMpInternalPurchase::getServiceCode,
						ConfMpInternalPurchase::getAppCode)
				.list()
				.stream()
				.collect(Collectors.toMap(internalPurchase -> internalPurchase.getOrderCode()
						+ "-" + internalPurchase.getSuborderCode()
						+ "-" + internalPurchase.getAbilityCode()
						+ "-" + internalPurchase.getServiceCode()
						+ "-" + internalPurchase.getAppCode(), ConfMpInternalPurchase::getId, (k1, k2) -> k1));
		// 4、遍历导入的数据集
		long failCount = 0;
		List<ErrorMessage> errorMessageList = (List<ErrorMessage>) bindingResult.getTarget();
		assert errorMessageList != null;
		Map<String, ConfMpInternalPurchase> map = new HashMap<>();
		for (ConfMpInternalPurchaseExcelVo purchase : excelVoList) {
			Set<String> errorMsg = new HashSet<>();
			String abilityCode = purchase.getAbilityCode();
			String abilityName = purchase.getAbilityName();
			String serviceCode = purchase.getServiceCode();
			String serviceName = purchase.getServiceName();
			String providerInsCode = purchase.getProviderInsCode();
			String providerInsName = purchase.getProviderInsName();
			String departmentCode = purchase.getDepartmentCode();
			String departmentName = purchase.getDepartmentName();
			String appCode = purchase.getAppCode();
			String appName = purchase.getAppName();
			String orderCode = purchase.getOrderCode();
			String suborderCode = purchase.getSuborderCode();
			String suborderTypeLabel = purchase.getSuborderType();
			if (StrUtil.isEmpty(suborderTypeLabel)) {
				suborderTypeLabel = "通过能运";
			}
			String suborderStatusLabel = purchase.getSuborderStatus();
			if (StrUtil.isEmpty(suborderStatusLabel)) {
				suborderStatusLabel = "执行完成";
			}
			String serviceGrantResultLabel = purchase.getServiceGrantResult();
			if (StrUtil.isEmpty(serviceGrantResultLabel)) {
				serviceGrantResultLabel = "成功";
			}
			// 非空校验
			if (!StrUtil.isAllNotBlank(abilityCode, abilityName, serviceCode, serviceName, providerInsCode,
					providerInsName, departmentCode, departmentName, appCode, appName, orderCode, suborderCode,
					suborderTypeLabel, suborderStatusLabel, serviceGrantResultLabel)) {
				errorMsg.add("非空校验不通过，请检查以下字段：能力订单编码、能力子订单编码、子订单类型、子订单状态、能力名称、能力编码、" +
						"服务提供方名称、服务提供方编码、服务名称、服务编码、订购部门名称、订购部门编码、订购应用名称、订购应用编码、服务开通结果");
				errorMessageList.add(new ErrorMessage(purchase.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			// 非法校验
			String suborderType = suborderTypeMap.get(suborderTypeLabel);
			if (StrUtil.isEmpty(suborderType)) {
				errorMsg.add("非法校验不通过：未知的子订单类型，请检查数据字典中是否有对应类型");
				errorMessageList.add(new ErrorMessage(purchase.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			String suborderStatus = suborderStatusMap.get(suborderStatusLabel);
			if (StrUtil.isEmpty(suborderStatus)) {
				errorMsg.add("非法校验不通过：未知的子订单状态，请检查数据字典中是否有对应类型");
				errorMessageList.add(new ErrorMessage(purchase.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			String serviceGrantResult = serviceGrantResultMap.get(serviceGrantResultLabel);
			if (StrUtil.isEmpty(serviceGrantResult)) {
				errorMsg.add("非法校验不通过：未知的服务开通结果，请检查数据字典中是否有对应类型");
				errorMessageList.add(new ErrorMessage(purchase.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			if (providerInsCode.length() < 3 || departmentCode.length() < 3) {
				errorMsg.add("非法校验不通过：服务提供方编码或订购部门编码的长度小于3，将会导致无法回填能力提供方单位编码和订购单位编码");
				errorMessageList.add(new ErrorMessage(purchase.getLineNum(), errorMsg));
				failCount++;
				continue;
			}
			ConfMpInternalPurchase.ConfMpInternalPurchaseBuilder builder = ConfMpInternalPurchase.builder();
			// 唯一校验：订单编码 + 子订单编码 + 能力编码 + 服务编码 + 订购应用编码
			String key = orderCode + "-" + suborderCode + "-" + abilityCode + "-" + serviceCode + "-" + appCode;
			Integer id = uniqueInternalMap.get(key);
			if (id != null) {
				// 存在重复的编码组合，订购单的处理方式是覆盖更新，所以这里创建实体对象时需要回填id
				builder.id(id);
			}
			// 通过校验，创建实体对象添加到待入库集合中，这里用map的作用是相同的key，后面的会覆盖前面的，起到覆盖更新的作用
			map.put(key, builder.abilityCode(abilityCode)
					.abilityName(abilityName)
					.serviceCode(serviceCode)
					.serviceName(serviceName)
					.providerInsCode(providerInsCode)
					.providerInsName(providerInsName)
					.providerOrgCode(providerInsCode.substring(0, 3))
					.orgCode(departmentCode.substring(0, 3))
					.departmentCode(departmentCode)
					.departmentName(departmentName)
					.appCode(appCode)
					.appName(appName)
					.orderCode(orderCode)
					.suborderCode(suborderCode)
					.suborderType(suborderType)
					.suborderStatus(suborderStatus)
					.serviceGrantResult(serviceGrantResult)
					.build());
		}
		// 5、批量写入内部订购单配置表
		Collection<ConfMpInternalPurchase> list = map.values();
		if (CollUtil.isNotEmpty(list)) {
			saveOrUpdateBatch(list, 1000);
		}
		return R.ok(errorMessageList, "导入完成，失败条数：" + failCount);
	}
}